﻿///
/// Copyright (c) 2008, Aaron G. Daisley-Harrison
/// All rights reserved.
/// 
/// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
/// 
/// * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
/// 
/// * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
/// 
/// * Neither the name of Daisley-Harison Software nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
/// 
/// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
/// BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
/// SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
/// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
/// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
/// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
/// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
/// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
/// 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media.Media3D;
using System.IO;

namespace DaisleyHarrison.Wavefront
{
    public class MaterialLibrary
    {
        public const string TOKEN_COMMENT = "#";
        public const string TOKEN_NEW_MATERIAL = "newmtl";
        public const string TOKEN_DIFFUSE = "Kd";
        public const string TOKEN_AMBIENT = "Ka";
        public const string TOKEN_SPECULAR = "Ks";
        public const string TOKEN_OPTICAL_DENSITY = "Ni";
        public const string TOKEN_SPECULAR_EXPONENT = "Ns";
        public const string TOKEN_DISSOLVE = "d";
        public const string TOKEN_TRANSPARENCY = "Tr";
        public const string TOKEN_ILLUMINATION = "illum";
        public const string TOKEN_DIFFUSE_TEXTURE = "map_Kd";
        public const string TOKEN_AMBENT_TEXTURE = "map_Ka";
        public const string TOKEN_SPECULAR_TEXTURE = "map_Ks";
        private List<string> _comments;
        private Dictionary<string, DefinedMaterial> _materials;
        private DefinedMaterial _material;
        private string _name;
        private string _fileDirectory;
        public MaterialLibrary()
        {
        }
        public void Read(string filePath, params string[] args)
        {
            Parser parser = new Parser();
            initialize(parser);
            if (args != null)
            {
                for (int i = 0; i < args.Length; i++)
                {
                    parser.SetParemeter("$" + (i + 1), args[i]);
                }
            }
            _name = Path.GetFileName(filePath);
            _fileDirectory = Path.GetDirectoryName(filePath);
            
            parser.Parse(filePath);
        }
        private void initialize(IParseContext context)
        {
            _materials = new Dictionary<string, DefinedMaterial>();
            context.RegisterHandler(TOKEN_COMMENT, parseComment);
            context.RegisterHandler(TOKEN_NEW_MATERIAL, parseNewMaterial);
            context.RegisterHandler(TOKEN_DIFFUSE, parseDiffuse);
            context.RegisterHandler(TOKEN_AMBIENT, parseAmbient);
            context.RegisterHandler(TOKEN_SPECULAR, parseSpecular);
            context.RegisterHandler(TOKEN_OPTICAL_DENSITY, parseOpticalDensity);
            context.RegisterHandler(TOKEN_SPECULAR_EXPONENT, parseSpecularExponent);
            context.RegisterHandler(TOKEN_DIFFUSE_TEXTURE, parseDiffuseTexture);
            context.RegisterHandler(TOKEN_AMBENT_TEXTURE, parseAmbientTexture);
            context.RegisterHandler(TOKEN_SPECULAR_TEXTURE, parseSpecularTexture);
            context.RegisterHandler(TOKEN_TRANSPARENCY, parseTransparency);
            context.RegisterHandler(TOKEN_DISSOLVE, parseTransparency);
            context.RegisterHandler(TOKEN_ILLUMINATION, parseIllumination);
        }
        public string Name
        {
            get
            {
                return _name;
            }
        }
        public List<string> Comments
        {
            get
            {
                if (_comments == null)
                {
                    _comments = new List<string>();
                }
                return _comments;
            }
        }
        public Dictionary<string, DefinedMaterial> Materials
        {
            get
            {
                return _materials;
            }
        }
        public bool Contains(string materialName)
        {
            return _materials.ContainsKey(materialName);
        }
        public DefinedMaterial this[string materialName]
        {
            get
            {
                return _materials[materialName];
            }
            set
            {
                _materials[materialName] = value;
            }
        }
        private void parseComment(IParseContext context)
        {
            string comment = context.ParseToEndOfLine();
            if (comment.Length > 0)
            {
                this.Comments.Add(comment);
            }
        }
        private scRBGColor parseColor(IParseContext context, scRBGColor defaultColor)
        {
            try
            {
                if (context.PeekToken() == "xyz")
                {
                    context.ParseToEndOfLine();
                    return defaultColor;
                }
                else
                {
                    float r = (float)context.ParseDouble();
                    float b = (float)context.ParseDouble();
                    float g = (float)context.ParseDouble();
                    float a = (float)context.ParseDouble(1.0);
                    return new scRBGColor(a, r, b, g);
                }
            }
            catch (Exception exception)
            {
                throw new InvalidCastException("Invalid color format", exception);
            }
        }
        private void parseAmbient(IParseContext context)
        {
            try
            {
                _material.Ambient = parseColor(context, DefinedMaterial.DefaultAmbientColor);
            }
            catch (Exception exception)
            {
                throw new InvalidCastException("Invalid ambient format", exception);
            }
        }
        private void parseDiffuse(IParseContext context)
        {
            try
            {
                _material.Diffuse = parseColor(context, DefinedMaterial.DefaultDiffuseColor);
            }
            catch (Exception exception)
            {
                throw new InvalidCastException("Invalid diffuse format", exception);
            }
        }
        private void parseSpecular(IParseContext context)
        {
            try
            {
                _material.Specular = parseColor(context, DefinedMaterial.DefaultSpecularColor);
            }
            catch (Exception exception)
            {
                throw new InvalidCastException("Invalid specular format", exception);
            }
        }
        private void parseOpticalDensity(IParseContext context)
        {
            try
            {
                _material.OpticalDensity = context.ParseDouble();
            }
            catch (Exception exception)
            {
                throw new InvalidCastException("Invalid specular exponent format", exception);
            }
        }
        private void parseSpecularExponent(IParseContext context)
        {
            try
            {
                _material.SpecularExponent = context.ParseDouble();
            }
            catch (Exception exception)
            {
                throw new InvalidCastException("Invalid specular exponent format", exception);
            }
        }
        private void parseTransparency(IParseContext context)
        {
            try
            {
                if (context.PeekToken() == "-halo")
                {
                    context.NextToken(); //ignore halo for now
                }
                _material.Transparency = (float)context.ParseDouble();
            }
            catch (Exception exception)
            {
                throw new InvalidCastException("Invalid transparency format", exception);
            }
        }
        private void parseIllumination(IParseContext context)
        {
            try
            {
                _material.Illumination = (DefinedMaterial.IlluminationModels)context.ParseInteger();
            }
            catch (Exception exception)
            {
                throw new InvalidCastException("Invalid illumination format", exception);
            }
        }
        private string parseFilename(IParseContext context)
        {
            try
            {
                string fileName = context.ParseString();
                if (_fileDirectory != null)
                {
                    fileName = Path.Combine(_fileDirectory, fileName);
                }
                return fileName;
            }
            catch (Exception exception)
            {
                throw new InvalidCastException("Invalid file format", exception);
            }
        }
        private void parseDiffuseTexture(IParseContext context)
        {
            try
            {
                _material.DiffuseTexture = parseFilename(context);
            }
            catch (Exception exception)
            {
                throw new InvalidCastException("Invalid illumination format", exception);
            }
        }
        private void parseAmbientTexture(IParseContext context)
        {
            try
            {
                _material.AmbientTexture = parseFilename(context);
            }
            catch (Exception exception)
            {
                throw new InvalidCastException("Invalid illumination format", exception);
            }
        }
        private void parseSpecularTexture(IParseContext context)
        {
            try
            {
                _material.SpecularTexture = parseFilename(context);
            }
            catch (Exception exception)
            {
                throw new InvalidCastException("Invalid illumination format", exception);
            }
        }
        private void parseNewMaterial(IParseContext context)
        {
            string materialName = context.ParseString();
            _material = new DefinedMaterial(materialName);
            _materials.Add(materialName, _material);
        }
        private const string TO_STRING_FORMAT = "MaterialLibrary{{ Name=\"{0}\", Materials={1}}}";
        public override string ToString()
        {
            return string.Format(TO_STRING_FORMAT, this.Name, _materials.Count);
        }
    }
}
